package com.niiwoo.civet.trade.service.local.creditor;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import org.apache.commons.collections.MapUtils;
import org.assertj.core.util.Lists;
import org.joda.time.DateTime;
import org.joda.time.Period;
import org.joda.time.PeriodType;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.CollectionUtils;

import com.alibaba.dubbo.config.annotation.Reference;
import com.google.common.collect.Maps;
import com.niiwoo.civet.trade.dao.entity.CreditorRateConfig;
import com.niiwoo.civet.trade.dao.entity.CreditorSaleBatch;
import com.niiwoo.civet.trade.dao.entity.CreditorSaleDetail;
import com.niiwoo.civet.trade.dao.entity.CreditorSelling;
import com.niiwoo.civet.trade.dao.entity.InvestorCreditor;
import com.niiwoo.civet.trade.dao.entity.Project;
import com.niiwoo.civet.trade.dao.entity.ProjectInvestorPlan;
import com.niiwoo.civet.trade.dao.mapper.CreditorRateConfigMapperExt;
import com.niiwoo.civet.trade.dao.mapper.CreditorSaleBatchMapperExt;
import com.niiwoo.civet.trade.dao.mapper.CreditorSaleDetailMapperExt;
import com.niiwoo.civet.trade.dao.mapper.CreditorSellingMapperExt;
import com.niiwoo.civet.trade.dao.mapper.InvestorCreditorMapperExt;
import com.niiwoo.civet.trade.dao.mapper.ProjectInvestorPlanMapperExt;
import com.niiwoo.civet.trade.dao.mapper.ProjectMapperExt;
import com.niiwoo.civet.trade.dao.mapper.RefundOrderMapperExt;
import com.niiwoo.civet.trade.dto.creditor.CreditorSaleDetailDTO;
import com.niiwoo.civet.trade.dto.request.creditor.CreditorSaleApplyRequestDTO;
import com.niiwoo.civet.trade.dto.request.creditor.CreditorSaleApplyRequestDTO.CreditorSaleApplyDetailDTO;
import com.niiwoo.civet.trade.dto.request.creditor.CreditorValidateRequestDTO;
import com.niiwoo.civet.trade.dto.response.creditor.CreditorSaleApplyResponseDTO;
import com.niiwoo.civet.trade.enums.InvestorCreditorStatusEnum;
import com.niiwoo.civet.trade.enums.ProjectStatusEnum;
import com.niiwoo.civet.trade.enums.ProjectSubStatusEnum;
import com.niiwoo.civet.trade.enums.RepaymentTypeEnum;
import com.niiwoo.civet.trade.enums.TradeConfigEnum;
import com.niiwoo.civet.trade.utils.RepaymentUtil;
import com.niiwoo.civet.user.dto.response.UserBasicInfoResponseDTO;
import com.niiwoo.civet.user.dto.response.UserIdentityInfoResponseDTO;
import com.niiwoo.civet.user.service.UserBasicInfoDubboService;
import com.niiwoo.civet.user.service.UserIdentityDubboService;
import com.niiwoo.tripod.base.component.PrivacyMasks;
import com.niiwoo.tripod.lanmao.component.LanMaoSequence;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;

import lombok.extern.slf4j.Slf4j;

/**
 * 债权出让申请服务 
 *
 * @author kevin.jia
 * @create 2018年7月6日 上午9:27:59
 */
@Slf4j
@Service
public class CreditorSaleOrderInitService {

    @Autowired
    private ProjectMapperExt projectMapperExt;
    
    @Autowired
    private RefundOrderMapperExt refundOrderMapperExt;
	
    @Autowired
    private CreditorSaleBatchMapperExt creditorSaleBatchMapperExt;

    @Autowired
    private CreditorSaleDetailMapperExt creditorSaleDetailMapperExt;
    
    @Autowired
    private CreditorRateConfigMapperExt creditorRateConfigMapperExt;
    
    @Autowired
    private InvestorCreditorMapperExt investorCreditorMapperExt;
    
    @Autowired
    private CreditorSellingMapperExt creditorSellingMapperExt;
    
    @Autowired
	private ProjectInvestorPlanMapperExt projectInvestorPlanMapperExt;
    
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;

    @Autowired
    private LanMaoSequence lanMaoSequence;

    @Autowired
    private TradableInvestOrderService tradableInvestOrderService;
    
    @Autowired
    private CreditorSaleOrderToLanMaoService creditorSaleOrderToLanMaoService;
    
    @Autowired
    private PrivacyMasks privacyMasks;
    
    @Reference(version="1.0.0")
    private UserBasicInfoDubboService userBasicInfoDubboService;
    
	@Reference(version = "1.0.0")
	private UserIdentityDubboService userIdentityDubboService;

	/**
     * 债权出让较验
     * 
     * @param requestDTO
     */
    public void validateSaleCreditor(CreditorValidateRequestDTO requestDTO){
		// 债权转让参数配置
		Map<String, String> tradeConfigMap = tradableInvestOrderService.getTradableInvestOrderLimitCondition();

    	// 全局配置（验证债权出让开关是否开启、是否活动开启时间）
    	validateGlobalConfiguration(tradeConfigMap);

    	// 数据以及数据校验
		InvestorCreditor investorCreditor = investorCreditorMapperExt.selectByInvestUserIdAndInvestOrderId(requestDTO.getUserId(), requestDTO.getInvestOrderId());
    	
		// 业务规则与数据较验
		validateBussinessRule(investorCreditor, tradeConfigMap, false);
    }

    /**
     * 债权出让申请入口
     * 
     * @param requestDTO
     * @return
     */
	public CreditorSaleApplyResponseDTO creditorSaleApply(CreditorSaleApplyRequestDTO requestDTO) {
		CreditorSaleApplyResponseDTO responseDTO = new CreditorSaleApplyResponseDTO();
		
		// 债权转让参数配置
		Map<String, String> tradeConfigMap = tradableInvestOrderService.getTradableInvestOrderLimitCondition();

		// 债权出让全局参数较验
		validateGlobalConfiguration(tradeConfigMap);
		
		// 创建债权出让订单(主业务逻辑)
		List<CreditorSaleDetail> creditorSaleDetailList = getProxyObject().doCreateCreditorSaleOrder(requestDTO, tradeConfigMap);
		
		// 后续处理
		creditorSaleAfterHandle(creditorSaleDetailList);
		
		// 返回结果
		List<Long> creditorSaleDetailIdList = creditorSaleDetailList.stream().map(CreditorSaleDetail::getId).collect(Collectors.toList());
		responseDTO.setTotalSaleAmount(requestDTO.getTotalSaleAmount());
		responseDTO.setCreditorSaleDetailIdList(creditorSaleDetailIdList);
		
		// 返回对象处理
        return responseDTO;
    }
	
	public void preCreditorSaleApply(CreditorSaleApplyRequestDTO requestDTO) {
		String userId = requestDTO.getUserId();
		List<CreditorSaleApplyDetailDTO> applyDetailDTOList = requestDTO.getCreditorSaleApplyDetailDTOList();

		// 债权转让参数配置
		Map<String, String> tradeConfigMap = tradableInvestOrderService.getTradableInvestOrderLimitCondition();

		// 债权出让全局参数较验
		validateGlobalConfiguration(tradeConfigMap);

		// 最大申请中的债权转让记录判断
		String debtMaxTransferCount = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_MAX_TRANSFER_COUNT.getConfigKey(), "10");
		Integer inProgressNum = countInProgressCreditorSaleOrder(requestDTO.getUserId());
		Integer applyNum = requestDTO.getCreditorSaleApplyDetailDTOList().size();
		if ((inProgressNum + applyNum) > Integer.valueOf(debtMaxTransferCount)) {
			log.error("###债权出让申请###转让中债权不得超过{}个，当前正在处理中：{}个,本次申请：{}个， UserId:{}",debtMaxTransferCount, inProgressNum, applyNum, userId);
			throw new BizException("TRD1000005", debtMaxTransferCount);
		}

		// 查询出本次申请的投资债权
		Set<Long> investOrderIds = applyDetailDTOList.stream().map(CreditorSaleApplyDetailDTO::getInvestOrderId).collect(Collectors.toSet());
		List<InvestorCreditor> investorCreditorList = investorCreditorMapperExt.selectByInvestOrderIds(investOrderIds);
		if (applyDetailDTOList.size() != investorCreditorList.size()) {
			log.error("###债权出让申请###批量转让申请个数与实际可转让个数不一致,UserId:{}", userId);
			throw new BizException("TRD1000014");
		}

		// 判断投资债权用户是否匹配
		for (InvestorCreditor investorCreditor : investorCreditorList) {
			if (!investorCreditor.getInvestUserId().equals(requestDTO.getUserId())) {
				log.error("###债权出让申请###投资订单关联用户与当前用户不匹配,InvestOrderId:{}, UserId:{}", investorCreditor.getInvestUserId(), userId);
				throw new BizException("TRD1000016");
			}
		}

		// 封装成内部对象（涉及单个债权出让的校验）
		List<CreditorSaleDetailDTO> creditorSaleDetailDTOList = bulidCreditorSaleDetailDTO(applyDetailDTOList, investorCreditorList, tradeConfigMap, userId);

		// 批量申请统计数据较验
		validateBatchRequestData(creditorSaleDetailDTOList, requestDTO);
    }

	/**
	 * 出让申请中的债权订单补偿任务
	 * 
	 * 针对调用懒猫单笔债权出让接口无响应或者异常，导致长时间停留在出让申请中的债权订单 
	 */
	public void debentureSaleCompensation() {
		Long beginTime = System.currentTimeMillis();
		log.info("###债权出让补偿###出让申请中的债权订单补偿任务开始");
		List<CreditorSaleDetail> creditorSaleDetailList = creditorSaleDetailMapperExt.selectNeedCompensationForDebentureSale();
		if (CollectionUtils.isEmpty(creditorSaleDetailList)) {
			log.info("###债权出让补偿###出让申请中的债权订单补偿任务，本次待处理记录为空。任务结束");
			return;
		}
		log.info("###债权出让补偿###出让申请中的债权订单补偿任务，本次待处理记录记录数：{}", creditorSaleDetailList.size());
		for (CreditorSaleDetail creditorSaleDetail : creditorSaleDetailList) {
			creditorSaleOrderToLanMaoService.debentureSaleCompensation(creditorSaleDetail);
		}
		log.info("###债权出让补偿###出让申请中的债权订单补偿任务完成，总耗时：{}ms", System.currentTimeMillis() - beginTime);
	}
	
	/**
	 * 定时删除可投资列表中已手轻的债权订单
	 */
	public void deleteSelloutCreditorOrder() {
		Long beginTime = System.currentTimeMillis();
		log.info("###债权出让###删除可投资列表中已售罄的债权订单任务开始");
		List<CreditorSelling> creditorSellingList = creditorSellingMapperExt.selectSelloutCreditorOrder(199, Integer.MAX_VALUE);
		if (CollectionUtils.isEmpty(creditorSellingList)) {
			log.info("###债权出让###删除可投资列表中已售罄的债权订单任务，本次待处理记录为空。任务结束");
			return;
		}
		log.info("###债权出让###删除可投资列表中已售罄的债权订单任务，本次待处理记录记录数：{}", creditorSellingList.size());
		for (CreditorSelling creditorSelling : creditorSellingList) {
			creditorSellingMapperExt.deleteByPrimaryKey(creditorSelling.getCreditorSaleDetailId());
		}
		log.info("###债权出让###删除可投资列表中已售罄的债权订单任务结束，总耗时：{}ms", System.currentTimeMillis() - beginTime);		
	}
	
	@Transactional(isolation = Isolation.READ_COMMITTED)
	public List<CreditorSaleDetail> doCreateCreditorSaleOrder(CreditorSaleApplyRequestDTO requestDTO, Map<String, String> tradeConfigMap) {
		String userId = requestDTO.getUserId();
		List<CreditorSaleApplyDetailDTO> applyDetailDTOList = requestDTO.getCreditorSaleApplyDetailDTOList();

		// 最大申请中的债权转让记录判断
		String debtMaxTransferCount = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_MAX_TRANSFER_COUNT.getConfigKey(), "10");
		Integer inProgressNum = countInProgressCreditorSaleOrder(requestDTO.getUserId());
		Integer applyNum = requestDTO.getCreditorSaleApplyDetailDTOList().size();
		if ((inProgressNum + applyNum) > Integer.valueOf(debtMaxTransferCount)) {
			log.error("###债权出让申请###转让中债权不得超过{}个，当前正在处理中：{}个,本次申请：{}个， UserId:{}",debtMaxTransferCount, inProgressNum, applyNum, userId);
			throw new BizException("TRD1000005", debtMaxTransferCount);
		}		
		
		// 查询出本次申请的投资债权并对其加锁
		Set<Long> investOrderIds = applyDetailDTOList.stream().map(CreditorSaleApplyDetailDTO::getInvestOrderId).collect(Collectors.toSet());
		List<InvestorCreditor> investorCreditorList = investorCreditorMapperExt.selectByInvestOrderIdsForUpdate(investOrderIds);
		if (applyDetailDTOList.size() != investorCreditorList.size()) {
			log.error("###债权出让申请###批量转让申请个数与实际可转让个数不一致,UserId:{}", userId);
			throw new BizException("TRD1000014");
		}
		
		// 判断投资债权用户是否匹配
		for (InvestorCreditor investorCreditor : investorCreditorList) {
			if (!investorCreditor.getInvestUserId().equals(requestDTO.getUserId())) {
				log.error("###债权出让申请###投资订单关联用户与当前用户不匹配,InvestOrderId:{}, UserId:{}", investorCreditor.getInvestUserId(), userId);
				throw new BizException("TRD1000016");				
			}
		}
		
		// 封装成内部对象（涉及单个债权出让的校验）
		List<CreditorSaleDetailDTO> creditorSaleDetailDTOList = bulidCreditorSaleDetailDTO(applyDetailDTOList, investorCreditorList, tradeConfigMap, userId);
		
		// 批量申请统计数据较验
		validateBatchRequestData(creditorSaleDetailDTOList, requestDTO);
		
		// 生成债权出让订单
		final List<CreditorSaleDetail> creditorSaleDetailList = saveCreditorSaleOrder(creditorSaleDetailDTOList, requestDTO, tradeConfigMap);
		
		// 事务提交完成、调用存管债权出让
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
            	CompletableFuture.runAsync(() -> {
            		creditorSaleOrderToLanMaoService.invokeLaoMaoToDebentureSale(creditorSaleDetailList);
            		
            		CreditorSaleBatch creditorSaleBatch = new CreditorSaleBatch();
            		creditorSaleBatch.setSaleUserId(requestDTO.getUserId());
            		creditorSaleBatch.setDetailCount(creditorSaleDetailList.size());
            		creditorSaleBatch.setSaleAmount(requestDTO.getTotalSaleAmount());
            		tradableInvestOrderService.sendDebtApplySuccessMessage(creditorSaleBatch);
            	});
            }
        });
        return creditorSaleDetailList;
	}
	
	/**
	 * 出让成功后续处理
	 * 
	 * @param creditorSaleDetailList
	 */
	public void creditorSaleAfterHandle(List<CreditorSaleDetail> creditorSaleDetailList) {
		
	}

	/**
	 * 构建内部对象（涉及单个债权出让校验逻辑）
	 *
	 * @param applyDetailDTOList
	 * 						申请债权明细
	 * @param investorCreditorList
	 * 						投资债权列表
	 * @param tradeConfigMap
	 * 						债权转让条件配置
	 * @param userId
	 * 						出让人用户ID
	 * @return
	 */
	private List<CreditorSaleDetailDTO> bulidCreditorSaleDetailDTO(List<CreditorSaleApplyDetailDTO> applyDetailDTOList,
			List<InvestorCreditor> investorCreditorList, Map<String, String> tradeConfigMap, String userId) {
		List<CreditorSaleDetailDTO> creditorSaleDetailDTOList = Lists.newArrayList();
		
		// 将请求详情列表转换成MAP
		Map<Long, CreditorSaleApplyDetailDTO> applyDetailDTOMap = applyDetailDTOList.stream()
				.collect(Collectors.toMap(CreditorSaleApplyDetailDTO::getInvestOrderId, v -> v, (k1, k2) -> k1));
		
		// 获取债权转让手续费率配置
		Map<Byte, Map<Integer, BigDecimal>> transferRateConfigMap = bulidTransferRateConfigMap();

		BigDecimal saleAmount = BigDecimal.ZERO; // 转让金额
		BigDecimal minSaleAmount = BigDecimal.ZERO; // 最高转让金额
		BigDecimal maxSaleAmount = BigDecimal.ZERO; // 最低转让金额
		for (InvestorCreditor investorCreditor : investorCreditorList) {
			// 单个债权数据以及业务规则校验
			validateBussinessRule(investorCreditor, tradeConfigMap, true);
			
			// 手续费率设置校验
			Map<Integer, BigDecimal> transferPeriodConfigMap = transferRateConfigMap.get(investorCreditor.getRepaymentType());
			if (null == transferPeriodConfigMap || !transferPeriodConfigMap.containsKey(investorCreditor.getReceivedPeriod())) {
				log.error("###债权出让申请###对应已回款期数手续费未配置,RepaymentType:{},receivedPeriod:{},UserId:{}", investorCreditor.getRepaymentType(), 
						investorCreditor.getReceivedPeriod(), userId);
				throw new BizException("TRD1000007");
			}
			
			// 手续费率校验
			CreditorSaleApplyDetailDTO applyDetailDTO = applyDetailDTOMap.get(investorCreditor.getInvestOrderId());
			BigDecimal expenseRate = transferPeriodConfigMap.get(investorCreditor.getReceivedPeriod());
			if (applyDetailDTO.getExpenseRate().compareTo(expenseRate) != 0) {
				log.error("###债权出让申请###请求手续费率与实际手续费率不一致,请求手续费率:{},实际手续费率:{},RepaymentType:{},receivedPeriod:{},UserId:{}", applyDetailDTO.getExpenseRate(), 
						expenseRate, investorCreditor.getRepaymentType(), investorCreditor.getReceivedPeriod(), userId);
				throw new BizException("TRD1000015");
			}
			
			// 构建内部DTO对象
			CreditorSaleDetailDTO creditorSaleDetailDTO = transformCreditorSaleDetailDTO(applyDetailDTOMap.get(investorCreditor.getInvestOrderId()), investorCreditor, 
					transferPeriodConfigMap.get(investorCreditor.getReceivedPeriod()), userId);
			saleAmount = creditorSaleDetailDTO.getSaleAmount();
			maxSaleAmount = creditorSaleDetailDTO.getOriginalSaleAmount();
			minSaleAmount = creditorSaleDetailDTO.getOriginalSaleAmount().multiply(new BigDecimal("0.98")).setScale(2, BigDecimal.ROUND_HALF_EVEN);
			if (saleAmount.compareTo(maxSaleAmount) > 0) {
				log.error("###债权出让申请###转让金额高于最高可转让金额，转让金额：{},最高可转让金额:{},InvestOrderId:{}", saleAmount, maxSaleAmount, investorCreditor.getInvestOrderId());
				throw new BizException("TRD1000009");
			}
			if (saleAmount.compareTo(minSaleAmount) < 0) {
				log.error("###债权出让申请###转让金额低于最低可转让金额，转让金额：{},最低可转让金额:{},InvestOrderId:{}", saleAmount, minSaleAmount, investorCreditor.getInvestOrderId());
				throw new BizException("TRD1000010");
			}
			creditorSaleDetailDTOList.add(creditorSaleDetailDTO);
		}
		return creditorSaleDetailDTOList;
	}
	
	/**
	 * 验证债权出让全局参数配置
	 *
	 * @param tradeConfigMap
	 */
	private void validateGlobalConfiguration(Map<String, String> tradeConfigMap) {
		// 是否开启债权转让 
		String debtOnOff = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_ON_OFF.getConfigKey(), "0");
		String debtBusOnOff = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_LAUNCH_ON_OFF.getConfigKey(), "0");
		if ("0".equals(debtOnOff) || "0".equals(debtBusOnOff)) {
			throw new BizException("TRD1000001");
		}
		
		// 是否开启时间内
		DateTime currentDate = new DateTime();
		String startTime = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_APPLY_TIME_BEGIN.getConfigKey(), "08:00");
		String endTime = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_APPLY_TIME_END.getConfigKey(), "22:00");
		DateTime dateTimeStart = new DateTime(currentDate.getYear(), currentDate.getMonthOfYear(), currentDate.getDayOfMonth(), Integer.parseInt(startTime.split(":")[0]),
				Integer.parseInt(startTime.split(":")[1]), 0);
		DateTime dateTimeEnd = new DateTime(currentDate.getYear(), currentDate.getMonthOfYear(),
				currentDate.getDayOfMonth(), Integer.parseInt(endTime.split(":")[0]), Integer.parseInt(endTime.split(":")[1]), 0);
        if (currentDate.getMillis() < dateTimeStart.getMillis() || currentDate.getMillis() > dateTimeEnd.getMillis()){
			throw new BizException("TRD1000002", startTime, endTime);
        }
	}
	
	/**
	 * 批量请求数据验证
	 * 
	 * @param creditorSaleDetailDTOList
	 * @param requestDTO
	 */
	private void validateBatchRequestData(List<CreditorSaleDetailDTO> creditorSaleDetailDTOList, CreditorSaleApplyRequestDTO requestDTO) {
		// 请求金额与实际金额是否匹配
		BigDecimal totalSaleAmount = creditorSaleDetailDTOList.stream().map(CreditorSaleDetailDTO::getSaleAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
		if (totalSaleAmount.compareTo(requestDTO.getTotalSaleAmount()) != 0) {
			log.error("###债权出让申请###申请金额与实际可转让金额不一致,申请金额:{},实际可转让金额：{},UserId:{}", requestDTO.getTotalSaleAmount(), totalSaleAmount, requestDTO.getUserId());
			throw new BizException("TRD1000008");
		}
	}
	
	/**
	 * 数据以及业务规则校验
	 * 
	 * @param investorCreditor
	 * 						投资债权
	 * @param projectInvestorPlan
	 * 						当前回款计划
	 * @param tradeConfigMap
	 * 						债权相关的参数配置
	 * @param isSumbitApply
	 * 						是否提交申请（5.3.5 增加申请预处理与提交申请都为true，区别提示语）
	 *
	 */
	private void validateBussinessRule(InvestorCreditor investorCreditor, Map<String, String> tradeConfigMap, Boolean isSumbitApply) {
		Integer debtMaxTransferCount = MapUtils.getInteger(tradeConfigMap, TradeConfigEnum.DEBT_MAX_TRANSFER_COUNT.getConfigKey(), 10);
		Integer debtLimitTransferTimes = MapUtils.getInteger(tradeConfigMap, TradeConfigEnum.DEBT_LIMIT_TRANSFER_TIMES.getConfigKey(), 1);
		Integer debtMinHandPeroid = MapUtils.getInteger(tradeConfigMap, TradeConfigEnum.DEBT_MIN_HAND_PERIOD.getConfigKey(), 3);
		Integer debtMinRemainPeriod = MapUtils.getInteger(tradeConfigMap, TradeConfigEnum.DEBT_MIN_REMAIN_PERIOD.getConfigKey(), 1);
		Integer debtLimitTransferDays = MapUtils.getInteger(tradeConfigMap, TradeConfigEnum.DEBT_LIMIT_TRANSFER_DAYS.getConfigKey(), 1);
		String debtMinTransferAmount = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_MIN_TRANSFER_AMOUNT.getConfigKey(), "100");
		String creditorQualificationPromptText = MapUtils.getString(tradeConfigMap, TradeConfigEnum.CREDITOR_QUALIFICATION_PROMPT_TEXT.getConfigKey(), "");
    	
		BigDecimal originalSaleAmount = BigDecimal.ZERO; // 可出让金额
		Byte status = investorCreditor.getStatus(); // 债权状态
		Integer receivedPeriod = investorCreditor.getReceivedPeriod(); // 已回款期数
		Integer surplusReceivedPeriod = investorCreditor.getTotalPeriod() - investorCreditor.getReceivedPeriod(); // 未回款期数
		Integer datediffForReceivedDate = RepaymentUtil.getOverdueDay(new Date(), investorCreditor.getReceivedDate()); // 距离还款日天数
		
		// 当期计息起始时间，上一期预计回款时间不为空上一期为准，上一期预计回款时间为空以满标时间
		Project project = projectMapperExt.selectByPrimaryKey(investorCreditor.getProjectId());
		Date currentPeriodInterestStartDate = null;
		if (investorCreditor.getLastPeriodReceivedDate() != null) {
			currentPeriodInterestStartDate = investorCreditor.getLastPeriodReceivedDate();
		} else {
			currentPeriodInterestStartDate = project.getFullSuccessTime();
		}
		
		// 优先判断状态，状态满足再判断金额
		if (isSumbitApply) {
			if (!ProjectStatusEnum.REPAYING.getStatus().equals(project.getStatus())
					|| ProjectSubStatusEnum.REPAYING_INIT.getSubStatus() != project.getSubStatus()
					|| InvestorCreditorStatusEnum.RECEIVE_OVERDUE.getStatus().equals(status)) {
				log.error("###债权出让申请###因借款人提前还款或转让规则变更，标的无法转让,investOrderId:{},UserId:{}", investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());
				throw new BizException("TRD1000012");
			}
			
			// 是否提前还款（等额本息、先息后还需要判断是否提前还款）
			if (InvestorCreditorStatusEnum.FINISH.getStatus().equals(status)
					|| ((RepaymentTypeEnum.AVERAGE_CAPITAL_PLUS_INTEREST.getValue().equals(investorCreditor.getRepaymentType())
							|| RepaymentTypeEnum.MONTHLY_INTEREST_PAYMENT_DUE.getValue().equals(investorCreditor.getRepaymentType()))
							&& new DateTime().plusMonths(1).toDate().before(investorCreditor.getReceivedDate()))) {
				log.error("###债权出让申请###因借款人提前还款或转让规则变更，标的无法转让,investOrderId:{},UserId:{}", investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());
				throw new BizException("TRD1000012");
			}
		} else {
			if (!ProjectStatusEnum.REPAYING.getStatus().equals(project.getStatus())
					|| ProjectSubStatusEnum.REPAYING_INIT.getSubStatus() != project.getSubStatus()
					|| InvestorCreditorStatusEnum.RECEIVE_OVERDUE.getStatus().equals(status)) {
				log.error("###债权出让申请###债权业务规则校验不通过,investOrderId:{},UserId:{}", investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());

				// 防止后台配置的时候直接存在换行符
				if (creditorQualificationPromptText.contains("\n")) creditorQualificationPromptText = creditorQualificationPromptText.replaceAll("\n", "");
				if (creditorQualificationPromptText.contains("\r")) creditorQualificationPromptText = creditorQualificationPromptText.replaceAll("\r", "");
				creditorQualificationPromptText = "\n" + creditorQualificationPromptText; //xml中会将换行符\n转成字符串导致前端无法实现换行
				throw new BizException("TRD1000006", creditorQualificationPromptText.replaceAll("\\|", "\n"));
			}

			// 是否提前还款
			if (InvestorCreditorStatusEnum.FINISH.getStatus().equals(status)
					|| ((RepaymentTypeEnum.AVERAGE_CAPITAL_PLUS_INTEREST.getValue().equals(investorCreditor.getRepaymentType())
							|| RepaymentTypeEnum.MONTHLY_INTEREST_PAYMENT_DUE.getValue().equals(investorCreditor.getRepaymentType()))
							&& new DateTime().plusMonths(1).toDate().before(investorCreditor.getReceivedDate()))) {
				log.error("###债权出让申请###因借款人提前还款，标的无法转让，请重新选择,investOrderId:{},UserId:{}", investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());
				throw new BizException("TRD1000013");
			}
		}
		
		// 可出让金额=待收本息+当期出让人应收利息
		Integer canGetDateNum = RepaymentUtil.getOverdueDay(currentPeriodInterestStartDate, new Date()) - 1; // 当期出让人应收利息天数
		Integer allGetDateNum = RepaymentUtil.getOverdueDay(currentPeriodInterestStartDate, investorCreditor.getReceivedDate()); // 当期总天数
		BigDecimal currentInterestForDay = investorCreditor.getCurrentInterest().divide(new BigDecimal(allGetDateNum), 6, BigDecimal.ROUND_HALF_EVEN); // 当期每一天的利息
		BigDecimal canGetInterest = currentInterestForDay.multiply(new BigDecimal(canGetDateNum)).setScale(2, BigDecimal.ROUND_HALF_EVEN); // 当期出让人应收利息		
		originalSaleAmount = investorCreditor.getCapital().subtract(investorCreditor.getReceivedCapital()).add(canGetInterest);
    	
		// 5.3.5 修改，较验是否可转让与实际提交申请需要作不同的提示语
		if (isSumbitApply) {
			if (originalSaleAmount.compareTo(new BigDecimal(debtMinTransferAmount)) < 0
					|| receivedPeriod < debtMinHandPeroid || surplusReceivedPeriod < debtMinRemainPeriod
					|| datediffForReceivedDate <= debtLimitTransferDays || canGetDateNum < 0) {
				log.error("###债权出让申请###因借款人提前还款或转让规则变更，标的无法转让,investOrderId:{},UserId:{}", investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());
				throw new BizException("TRD1000012");
			}
		} else {
			// 回款逾期、已结清、可转让债权金额小于最低限度、已回期期限小于最低限度、待收期数小于最低限度、回款日期至少提前一天
			if (originalSaleAmount.compareTo(new BigDecimal(debtMinTransferAmount)) < 0
					|| receivedPeriod < debtMinHandPeroid || surplusReceivedPeriod < debtMinRemainPeriod
					|| datediffForReceivedDate <= debtLimitTransferDays || canGetDateNum < 0) {
				log.error("###债权出让申请###债权业务规则校验不通过,investOrderId:{},UserId:{}", investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());

				// 防止后台配置的时候直接存在换行符
				if (creditorQualificationPromptText.contains("\n")) creditorQualificationPromptText = creditorQualificationPromptText.replaceAll("\n", "");
				if (creditorQualificationPromptText.contains("\r")) creditorQualificationPromptText = creditorQualificationPromptText.replaceAll("\r", "");
				creditorQualificationPromptText = "\n" + creditorQualificationPromptText; //xml中会将换行符\n转成字符串导致前端无法实现换行
				throw new BizException("TRD1000006", creditorQualificationPromptText.replaceAll("\\|", "\n"));
			}
		}
		
		// 债权转让次数
		if (investorCreditor.getTransferFrequency() >= debtLimitTransferTimes) {
			log.error("###债权出让申请###单个债权最多可转让{}次,investOrderId:{},UserId:{}", debtLimitTransferTimes, investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());
			throw new BizException("TRD1000011", debtLimitTransferTimes);
		}

		// 债权出让申请正在处理中
		if (isAlreadyApplyCreditorSaleOrder(investorCreditor.getInvestUserId(), investorCreditor.getInvestOrderId())) {
			log.error("###债权出让申请###债权出让申请正在处理中, InvestOrderId:{}, UserId:{}", investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());			
			throw new BizException("TRD1000004");
		}
		
		// 转让中债权最大数量
		if (countInProgressCreditorSaleOrder(investorCreditor.getInvestUserId()) >= debtMaxTransferCount) {
			log.error("###债权出让申请###转让中债权不得超过{}个,UserId:{}", debtMaxTransferCount, investorCreditor.getInvestUserId());			
			throw new BizException("TRD1000005", debtMaxTransferCount);
		}
		
		// 标的是否有正在进行的还款
		if (isHadInProgressRefundOrder(investorCreditor.getProjectId())) {
			log.error("###债权出让申请###关联的标的正在还款中,ProjectId:{}, InvestOrderId:{}, UserId:{}",
					investorCreditor.getProjectId(), investorCreditor.getInvestOrderId(), investorCreditor.getInvestUserId());
			throw new BizException("TRD1000003");
		}
	}
	
	/**
	 * 生成债权出让订单
	 * 
	 * @param creditorSaleDetailDTOList
	 * @param requestDTO
	 * @param tradeConfigMap
	 * @return
	 */
	private List<CreditorSaleDetail> saveCreditorSaleOrder(List<CreditorSaleDetailDTO> creditorSaleDetailDTOList,
			CreditorSaleApplyRequestDTO requestDTO, Map<String, String> tradeConfigMap) {
		DateTime currentDate = new DateTime();
		
		List<CreditorSaleDetail> creditorSaleDetailList = new ArrayList<>(creditorSaleDetailDTOList.size());
		// 出让人信息
		UserBasicInfoResponseDTO userBasicInfo = userBasicInfoDubboService.getUserBasicInfo(requestDTO.getUserId());
		// 出让人身份信息
		UserIdentityInfoResponseDTO userIdentityInfo = userIdentityDubboService.get(requestDTO.getUserId());
		
		// 生成债权出让批次记录
		CreditorSaleBatch creditorSaleBatch = new CreditorSaleBatch();
		Long creditorSaleBatchId = snowflakeIdWorker.nextId();
		creditorSaleBatch.setId(creditorSaleBatchId);
		creditorSaleBatch.setSaleUserId(requestDTO.getUserId());
		creditorSaleBatch.setDetailCount(creditorSaleDetailDTOList.size());
		creditorSaleBatch.setSaleAmount(requestDTO.getTotalSaleAmount());
		creditorSaleBatch.setIsBatch(requestDTO.getIsBatch());
		creditorSaleBatch.setCreateTime(currentDate.toDate());
		creditorSaleBatch.setUpdateTime(currentDate.toDate());
		creditorSaleBatchMapperExt.insertSelective(creditorSaleBatch);
		
		// 生成债权出让明细订单记录
		for (CreditorSaleDetailDTO creditorSaleDetailDTO : creditorSaleDetailDTOList) {
			CreditorSaleDetail creditorSaleDetail = new CreditorSaleDetail();

			creditorSaleDetail.setId(snowflakeIdWorker.nextId());
			creditorSaleDetail.setRequestNo(lanMaoSequence.getRequestNo());
			creditorSaleDetail.setBatchId(creditorSaleBatchId);
			creditorSaleDetail.setInvestOrderId(creditorSaleDetailDTO.getInvestOrderId());

			// 标的信息
			Project project = projectMapperExt.selectByPrimaryKey(creditorSaleDetailDTO.getProjectId());
			creditorSaleDetail.setProjectId(project.getProjectId());
			creditorSaleDetail.setBorrowRate(project.getBorrowRate());
			creditorSaleDetail.setDeadline(project.getDeadline());
			creditorSaleDetail.setRepaymentType(project.getRepaymentType());
			creditorSaleDetail.setBorrowerType(project.getBorrowerType());

			// 出让人信息
			creditorSaleDetail.setUserId(creditorSaleDetailDTO.getUserId());
			creditorSaleDetail.setRealName(userIdentityInfo.getName());
			creditorSaleDetail.setMobile(privacyMasks.encryptPrivacy(userBasicInfo.getMobileNo()));
			creditorSaleDetail.setNickName(userBasicInfo.getNickName());
			creditorSaleDetail.setGender(userBasicInfo.getGender());
			creditorSaleDetail.setHeadImage(userBasicInfo.getHeadImage());
			creditorSaleDetail.setNiiwooScore(userBasicInfo.getNiiwooScore());

			// 帐户信息
			ProjectInvestorPlan projectInvestorPlan = projectInvestorPlanMapperExt.getCurrentPeriodInvestorPlan(
					creditorSaleDetailDTO.getInvestOrderId(), creditorSaleDetailDTO.getUserId(), creditorSaleDetailDTO.getCurrentPeriod());
			creditorSaleDetail.setAccountId(projectInvestorPlan.getAccountId());
			creditorSaleDetail.setPlatformUserNo(projectInvestorPlan.getPlatformUserNo());

			// 债权信息
			DateTime curPeriodReceivedDate = new DateTime(creditorSaleDetailDTO.getReceivedDate());

			creditorSaleDetail.setRemainingDeadlineDay(new Period(currentDate.withMillisOfDay(0), curPeriodReceivedDate, PeriodType.days()).getDays());
			creditorSaleDetail.setRemainingDeadlineDay(creditorSaleDetail.getRemainingDeadlineDay() + 1); //发起债转的日期需要给到受让人
			creditorSaleDetail.setRemainingDeadlineMonth(creditorSaleDetailDTO.getTotalPeriod() - creditorSaleDetailDTO.getCurrentPeriod()); // 防止造数据问题
			creditorSaleDetail.setEndDate(creditorSaleDetailDTO.getEndDate());

			creditorSaleDetail.setSaleAmount(creditorSaleDetailDTO.getSaleAmount());
			creditorSaleDetail.setShare(creditorSaleDetailDTO.getShare());
			creditorSaleDetail.setInterest(creditorSaleDetailDTO.getInterest());
			creditorSaleDetail.setOffPercent(creditorSaleDetailDTO.getOffPercent());
			creditorSaleDetail.setOffAmount(creditorSaleDetailDTO.getOffAmount());
			creditorSaleDetail.setExpenseRate(creditorSaleDetailDTO.getExpenseRate());
			creditorSaleDetail.setExpenseAmount(creditorSaleDetailDTO.getExpenseAmount());
			creditorSaleDetail.setArrivalAmount(creditorSaleDetailDTO.getArrivalAmount());
			creditorSaleDetail.setCurrentInterestForDay(creditorSaleDetailDTO.getCurrentInterestForDay());

			// 债权转让购买时效（计算出让过期时间）
			String debtTransferTimeLimit = MapUtils.getString(tradeConfigMap, TradeConfigEnum.DEBT_TRANSFER_TIME_LIMIT.getConfigKey(), "24");
			creditorSaleDetail.setExpireTime(currentDate.plusHours(Integer.valueOf(debtTransferTimeLimit)).toDate());
			creditorSaleDetail.setCreateTime(currentDate.toDate());
			creditorSaleDetail.setUpdateTime(currentDate.toDate());

			creditorSaleDetailMapperExt.insertSelective(creditorSaleDetail);
			
			creditorSaleDetailList.add(creditorSaleDetail);
		}

		return creditorSaleDetailList;
	}

	/**
	 * 判断是否有申请成功的债权订单
	 * 
	 * @param investOrderId
	 * @param userId
	 * @return
	 */
	private Boolean isAlreadyApplyCreditorSaleOrder(String userId, Long investOrderId) {
		return creditorSaleDetailMapperExt.getAppliedSuccessfullyOrderByUserIdAndInvestOrderId(userId, investOrderId) != null;
	}
	
	/**
	 * 统计用户在在处理中的债权出让记录
	 * @param userId
	 * @return
	 */
	private Integer countInProgressCreditorSaleOrder(String userId) {
		return creditorSaleDetailMapperExt.countInProgressOrderByUserIdAndInvestOrderId(userId, null);
	}
	
	/**
	 * 标的是否有正在处理的还款订单
	 * 
	 * @param projectId
	 * @return
	 */
	private Boolean isHadInProgressRefundOrder(Long projectId) {
		return refundOrderMapperExt.selectInProgressOrderCount(projectId) > 0 ? true : false;
	}
	
	/**
	 * 转出手续费配置
	 * 数据格式：<RepaymentType, <Period, TransferRate>>
	 * 
	 * @return
	 */
	private Map<Byte, Map<Integer, BigDecimal>> bulidTransferRateConfigMap() {
		Map<Byte, Map<Integer, BigDecimal>> transferRateConfigMap = new HashMap<>();
		List<CreditorRateConfig> creditorRateConfigList = creditorRateConfigMapperExt.getAllCreditorRateConfigService();
		for (CreditorRateConfig creditorRateConfig : creditorRateConfigList) {
			Map<Integer, BigDecimal> transferPeriodConfigMap = transferRateConfigMap.get(creditorRateConfig.getRepaymentType());
			if (MapUtils.isEmpty(transferPeriodConfigMap)) {
				transferPeriodConfigMap = Maps.newHashMap();
			}
			int minPeriod = creditorRateConfig.getMinCollectedPeriods();
			int maxPeriod = creditorRateConfig.getMaxCollectedPeriods();
			for (int period = minPeriod; period <= maxPeriod; period++) {
				transferPeriodConfigMap.put(period, creditorRateConfig.getTransferRate());
			}				
			transferRateConfigMap.put(creditorRateConfig.getRepaymentType(), transferPeriodConfigMap);
		}
		return transferRateConfigMap;
	}

	/**
	 * 将请求详情转换成内部出让详情
	 * 
	 * @param applyDetailDTO
	 * @param investorCreditor
	 * @param expenseRate
	 * @param userId
	 * @return
	 */
	private CreditorSaleDetailDTO transformCreditorSaleDetailDTO(CreditorSaleApplyDetailDTO applyDetailDTO,
			InvestorCreditor investorCreditor, BigDecimal expenseRate, String userId) {
		
		CreditorSaleDetailDTO creditorSaleDetailDTO = new CreditorSaleDetailDTO();
		
		creditorSaleDetailDTO.setUserId(userId);
		creditorSaleDetailDTO.setProjectId(investorCreditor.getProjectId());
		creditorSaleDetailDTO.setInvestOrderId(applyDetailDTO.getInvestOrderId());
		creditorSaleDetailDTO.setTotalPeriod(investorCreditor.getTotalPeriod());
		creditorSaleDetailDTO.setReceivedPeriod(investorCreditor.getReceivedPeriod());
		creditorSaleDetailDTO.setReceivedDate(investorCreditor.getReceivedDate());
		creditorSaleDetailDTO.setEndDate(investorCreditor.getExpectOverDate());
		creditorSaleDetailDTO.setCurrentPeriod(investorCreditor.getCurrentPeriod());
		
		// 当期计息起始时间，上一期预计回款时间不为空上一期为准，上一期预计回款时间为空以满标时间
		Date currentPeriodInterestStartDate = null;
		if (investorCreditor.getLastPeriodReceivedDate() != null) {
			currentPeriodInterestStartDate = investorCreditor.getLastPeriodReceivedDate();
		} else {
			Project project = projectMapperExt.selectByPrimaryKey(investorCreditor.getProjectId());
			currentPeriodInterestStartDate = project.getFullSuccessTime();
		}

		// 原始债权金额=待收本金+当期应收利息
		Integer canGetDateNum = RepaymentUtil.getOverdueDay(currentPeriodInterestStartDate, new Date()) - 1; // 出让人应收利息天数
		Integer allGetDateNum = RepaymentUtil.getOverdueDay(currentPeriodInterestStartDate, investorCreditor.getReceivedDate()); // 当前计算总天数
		BigDecimal currentInterestForDay = investorCreditor.getCurrentInterest().divide(new BigDecimal(allGetDateNum), 6, BigDecimal.ROUND_HALF_EVEN); // 当期每一天的利息
		BigDecimal canGetInterest = currentInterestForDay.multiply(new BigDecimal(canGetDateNum)).setScale(2, BigDecimal.ROUND_HALF_EVEN); // 出让人应收利息
		BigDecimal originalSaleAmount = investorCreditor.getCapital().subtract(investorCreditor.getReceivedCapital()).add(canGetInterest);
		creditorSaleDetailDTO.setOriginalSaleAmount(originalSaleAmount);
		creditorSaleDetailDTO.setCurrentInterestForDay(currentInterestForDay);
		
		// ##########################债权出让金额相关信息###################################
		
		// 出让金额=原始债权金额-让利金额 (让利金额=原始债权金额*降价比率)
		BigDecimal offPercent = applyDetailDTO.getOffPercent();
		BigDecimal offAmount = originalSaleAmount.multiply(applyDetailDTO.getOffPercent()).setScale(2, BigDecimal.ROUND_HALF_EVEN);
		BigDecimal saleAmount = originalSaleAmount.subtract(offAmount);
		
		// 手续费=出让金额*手续费率
		BigDecimal expenseAmount = saleAmount.multiply(expenseRate).setScale(2, BigDecimal.ROUND_HALF_EVEN);
		// 债权出让到手金额=出让金额-手续费率
		BigDecimal arrivalAmount = saleAmount.subtract(expenseAmount);
		// 债权份额（即待收本金）
		BigDecimal share = investorCreditor.getCapital().subtract(investorCreditor.getReceivedCapital());
		// 预期利息
		BigDecimal interest = investorCreditor.getInterest().subtract(investorCreditor.getReceivedInterest()).subtract(canGetInterest);
		
		creditorSaleDetailDTO.setSaleAmount(saleAmount);
		creditorSaleDetailDTO.setShare(share);
		creditorSaleDetailDTO.setInterest(interest);
		creditorSaleDetailDTO.setOffPercent(offPercent);
		creditorSaleDetailDTO.setOffAmount(offAmount);
		creditorSaleDetailDTO.setExpenseRate(expenseRate);
		creditorSaleDetailDTO.setExpenseAmount(expenseAmount);
		creditorSaleDetailDTO.setArrivalAmount(arrivalAmount);
		
		return creditorSaleDetailDTO;
	}
	
	public CreditorSaleOrderInitService getProxyObject() {
		return (CreditorSaleOrderInitService) AopContext.currentProxy();
	}
}